package impl_base

import (
	"io"
	"math"
	"sync/atomic"

	"gitee.com/hasika/common-server/conn/fd"
	"gitee.com/hasika/common-server/conn/inet"
)

var globalConnectionId int64

func getNextConnectionId() int64 {
	return atomic.AddInt64(&globalConnectionId, 1)
}

type Connection struct {
	ReadCtx         *inet.ReadCtx
	WriteCtx        inet.IWriteCtx
	call            inet.ConnectionCallback
	conn            inet.Conn
	fd              int
	closed          *atomic.Bool
	connectionId    int64
	session         atomic.Pointer[inet.Session]
	WriteBytes      atomic.Uint64
	ReadBytes       atomic.Uint64
	recordReadWrite bool
}

func NewBaseConnection() *Connection {
	return &Connection{
		ReadCtx: inet.NewReadCtx(),
		closed:  &atomic.Bool{},
	}
}

func (t *Connection) InitConnection(conn inet.Conn, callback inet.ConnectionCallback, nonblocking bool, writeCtx inet.IWriteCtx, record bool) {
	t.conn = conn
	t.call = callback
	t.WriteCtx = writeCtx
	t.connectionId = getNextConnectionId()
	t.recordReadWrite = record
	conn.InitFd()
	if t.fd != 0 && nonblocking {
		//阻塞式不要设置值,会导致无法关闭
		fd.SetNonBlock(t.fd, true)
	}
	callback.OnConnectionStart(conn)
}

func (t *Connection) GetWriteCtx() inet.IWriteCtx {
	return t.WriteCtx
}

func (t *Connection) GetReadCtx() *inet.ReadCtx {
	return t.ReadCtx
}

func (t *Connection) CloseConnectionAllCallback(string) {
	oldStatus := t.closed.Swap(true)
	if oldStatus == false {
		t.conn.CloseConn()
		inet.Log.Infof("connection closed %s,write bytes len %d,read bytes len %d", t.conn.GetRemote().String(), t.WriteBytes.Load(), t.ReadBytes.Load())
		t.call.OnConnectionEnd(t.conn)
		t.session.Store(nil)
	}
	return
}

func (t *Connection) GetFd() int {
	return t.fd
}

func (t *Connection) SetFd(fd int) {
	t.fd = fd
}

func (t *Connection) GetConnectionId() int64 {
	return t.connectionId
}

func (t *Connection) GetSession() *inet.Session {
	return t.session.Load()
}

func (t *Connection) SetSession(session *inet.Session) {
	t.session.Store(session)
}

func (t *Connection) WriteWithLimit(writer io.Writer, data []byte) error {
	start := 0
	maxLen := len(data)
	for {
		currentWriteLen := int(math.Min(inet.BufferMaxLen, float64(maxLen-start)))
		writeLen, writeErr := writer.Write(data[start : start+currentWriteLen])
		if writeErr != nil {
			inet.Log.Warnf("write error %+v", writeErr)
			return writeErr
		}
		start += writeLen
		if start >= maxLen {
			break
		}
	}
	t.RecordWrite(maxLen)
	return nil
}

//go:force inline
func (t *Connection) RecordRead(n int) {
	if t.recordReadWrite {
		t.ReadBytes.Add(uint64(n))
	}
}

//go:force inline
func (t *Connection) RecordWrite(n int) {
	if t.recordReadWrite {
		t.WriteBytes.Add(uint64(n))
	}
}
